package com.zyx.javademo.clazz;

import java.util.List;

/**
 * @author Yaxi.Zhang
 * @since 2021/7/6 14:36
 * desc: Java逆变与协变 ==> PECS原则
 * @see <a href="https://www.cnblogs.com/en-heng/p/5041124.html">java逆变与协变</a>
 * 在介绍逆变与协变之前，先引入Liskov替换原则（Liskov Substitution Principle, LSP）。
 *
 * Liskov替换原则
 *      LSP由`Barbara Liskov`于1987年提出，其定义如下：
 *
 * 所有引用基类（父类）的地方必须能透明地使用其子类的对象。
 *
 * LSP包含以下四层含义：
 *      子类完全拥有父类的方法，且具体子类必须实现父类的抽象方法。
 *      子类中可以增加自己的方法。
 *      当子类覆盖或实现父类的方法时，方法的形参要比父类方法的更为宽松。
 *      当子类覆盖或实现父类的方法时，方法的返回值要比父类更严格。
 *      前面的两层含义比较好理解，后面的两层含义会在下文中详细解释。根据LSP，我们在实例化对象的时候，可以用其子类进行实例化，比如：
 *          Number num = new Integer(1);
 *
 * 逆变与协变用来描述类型转换（type transformation）后的继承关系，其定义：如果A、B表示类型，f(⋅)表示类型转换，≤表示继承关系（比如，A≤B表示A是由B派生出来的子类）；
 *      f(⋅)是逆变（contravariant）的，当 A≤B 时有f(B)≤f(A)成立；
 *      f(⋅)是协变（covariant）的， 当 A≤B 时有f(A)≤f(B)成立；
 *      f(⋅)是不变（invariant）的， 当 A≤B 时上述两个式子均不成立，即f(A)与f(B)相互之间没有继承关系。
 *
 * 类型转换
 * 接下来，我们看看Java中的常见类型转换的协变性、逆变性或不变性。
 *
 * 泛型
 *      令f(A)=ArrayList<A>，那么f(⋅)时逆变、协变还是不变的呢？
 *      如果是逆变，则ArrayList<Integer>是ArrayList<Number>的父类型；
 *      如果是协变，则ArrayList<Integer>是ArrayList<Number>的子类型；
 *      如果是不变，二者没有相互继承关系。开篇代码中
 *
 * 数组
 *      令f(A)=[]A，容易证明数组是协变的：
 *      Number[] numbers = new Integer[3];
 * 方法
 *      调用方法result = method(n)；
 *      根据Liskov替换原则，传入形参n的类型应为method形参的子类型，即typeof(n) ≤ typeof(method's parameter)；
 *      result应为method返回值的基类型，即typeof(methods's return) ≤ typeof(result)
 *      在Java 1.4中，子类覆盖（override）父类方法时，形参与返回值的类型必须与父类保持一致
 *      从Java 1.5开始，子类覆盖父类方法时允许协变返回更为具体的类型
 *      class Super {
 *          Number method(Number n) { ... }
 *       }
 *
 *      class Sub extends Super {
 *          // @Override
 *          Integer method(Number n) { ... }
 *      }
 *
 * 实现泛型的协变与逆变
 * Java中泛型是不变的，可有时需要实现逆变与协变，怎么办呢？这时，通配符?派上了用场：
 *      <? extends>实现了泛型的协变，比如：
 *      List<? extends Number> list = new ArrayList<Integer>();
 *      <? super>实现了泛型的逆变，比如：
 *      List<? super Number> list = new ArrayList<Object>();
 *
 * 现在问题来了：究竟什么时候用extends什么时候用super呢？《Effective Java》给出了答案：
 *      `PECS: producer-extends, consumer-super.` !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 *      参考 {@link java.util.Collections#copy(List, List)}，完美地诠释了PECS
 *      在取出src中的数据时使用了extends关键字，而在为dest赋值时则使用了super关键字
 *
 * PECS总结
 *      要从泛型类取数据时，用extends；
 *      要往泛型类写数据时，用super；
 *      既要取又要写，就不用通配符（即extends与super都不用）。
 *
 * 参考资料
 *      @see <a href="https://stackoverflow.com/questions/8481301/covariance-invariance-and-contravariance-explained-in-plain-english">
 *          Covariance, Invariance and Contravariance</a>
 *      @see <a href="https://stackoverflow.com/questions/4343202/difference-between-super-t-and-extends-t-in-java">
 *          Difference between <? super T> and <? extends T> in Java [duplicate]</a>
 *      Joshua Bloch, 《Effective Java》
 */
public class ContravariantVsCovariant {

    public static void main(String[] args) {
        /*
        // 用ArrayList<Integer>实例化list的对象错误，则说明泛型是不变的。
        Number num = new Integer(1);
        // type mismatch
        ArrayList<Number> list = new ArrayList<Integer>(); */

        /*

        List<? extends Number> list = new ArrayList<Number>();
        // error
        list.add(new Integer(1));
        // error
        list.add(new Float(1.2f));
        // add方法
        public interface List<E> extends Collection<E> {
            boolean add(E e);
        }
        在调用add方法时，泛型E自动变成了<? extends Number>，其表示list所持有的类型为在Number与Number派生子类中的某一类型，
        其中包含Integer类型却又不特指为Integer类型（Integer像个备胎一样！！！），故add Integer时发生编译错误。为了能调用add方法，可以用super关键字实现：
         */
    }
}
